/*
 * Copyright (c) 2009 Mikolaj Golub
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/errno.h>
#include <sys/resource.h>

#include <err.h>
#include <ctype.h>
#include <fcntl.h>
#include <kvm.h>
#include <nlist.h>
#include <unistd.h>
#include <paths.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>

#include <sys/resource.h>
#include <sys/pcpu.h>
#include <sys/vmmeter.h>
#include <sys/time.h>
#include <sys/namei.h>
#include <sys/malloc.h>
#include <vm/uma.h>
#include <vm/uma_int.h>
#define _WANT_UCRED     /* make ucred.h give us 'struct ucred' */
#include <sys/ucred.h>
#include <sys/proc.h>
#include <sys/user.h>
#define _KVM_VNODE
#include <sys/vnode.h>
#if __FreeBSD_version < 800000
#define _KERNEL
#else
#define _WANT_FILE
#endif
#include <sys/file.h>
#include <sys/conf.h>
#define _KERNEL
#include <sys/pipe.h>
#include <sys/mount.h>
#include <ufs/ufs/quota.h>
#include <ufs/ufs/inode.h>
#include <fs/devfs/devfs.h>
#include <fs/devfs/devfs_int.h>
#include <fs/msdosfs/bpb.h>
#include <fs/msdosfs/msdosfsmount.h>
#undef  _KERNEL
#include <fs/msdosfs/denode.h>
#include <nfs/nfsproto.h>
#if __FreeBSD_version < 800000
#include <nfs/rpcv2.h>
#endif
#include <nfsclient/nfs.h>
#include <nfsclient/nfsnode.h>
#include <sys/tty.h>
#include <sys/sysent.h>
#if __FreeBSD_version < 800000
#include <sys/kse.h>
#endif
#include <sys/_task.h>
#define _WANT_PRISON    /* make jail.h give us 'struct prison' */
#include <sys/jail.h>
#include <sys/signalvar.h>
#include <sys/resourcevar.h>
#include <vm/swap_pager.h>
#include <sys/mbuf.h>

#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/domain.h>
#include <sys/protosw.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_carp.h>
#include <netinet/ip_var.h>
#define TCPSTATES
#include <netinet/in_pcb.h>
#include <netinet/tcp_var.h>
#include <netinet/udp.h>
#include <netinet/udp_var.h>
#include <netinet/sctp_uio.h>
#include <netinet/ip_icmp.h>
#include <netinet/icmp_var.h>
#include <netinet/igmp_var.h>
#include <netipsec/ipsec.h>
#include <netipsec/ah_var.h>
#include <netipsec/esp_var.h>
#include <netipsec/ipcomp_var.h>
#include <netinet/pim_var.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_dl.h>
#include <net/pfvar.h>
#include <net/if_pfsync.h>
#include <netinet/in.h>
#include <netinet/in_var.h>
#include <netipx/ipx.h>
#include <netipx/ipx_if.h>
#include <sys/un.h>
#include <sys/unpcb.h>

#include <setjmp.h>

#include <machine/minidump.h>
#include "pkvmdump.h"

static struct nlist namelist[] = {
#define X_SUM		0
	{ .n_name = "_cnt" },
#define	X_BOOTTIME	1
	{ .n_name = "_boottime" },
#define X_TICKS		2
	{ .n_name = "_ticks" },
#define X_HZ		3
	{ .n_name = "_hz" },
#define X_STATHZ	4
	{ .n_name = "_stathz" },
#define X_NCHSTATS	5
	{ .n_name = "_nchstats" },
#define	X_INTRNAMES	6
	{ .n_name = "_intrnames" },
#define	X_EINTRNAMES	7
	{ .n_name = "_eintrnames" },
#define	X_INTRCNT	8
	{ .n_name = "_intrcnt" },
#define	X_EINTRCNT	9
	{ .n_name = "_eintrcnt" },
#define	X_KMEMSTATS	10
	{ .n_name = "_kmemstatistics" },
#define	X_KMEMZONES	11
	{ .n_name = "_kmemzones" },
#define X_MP_MAXCPUS	12
        { .n_name = "_mp_maxcpus" },
#define X_CPUID_TO_PCPU 13
	{ .n_name = "_cpuid_to_pcpu" },	
#define X_UMA_KEGS	14
        { .n_name = "_uma_kegs" },
#define X_MP_MAXID	15
        { .n_name = "_mp_maxid" },
#define X_ALL_CPUS	16
        { .n_name = "_all_cpus" },
#define X_NPROCS	17
        { .n_name = "_nprocs" },
#define X_ALLPROC	18
        { .n_name = "_allproc" },
#define X_ZOMBPROC	19
        { .n_name = "_zombproc" },
#define X_MAXFILES	20
	{ .n_name = "_maxfiles" },
#define X_OPENFILES	21
	{ .n_name = "_openfiles" },
#define X_FILEHEAD	22
	{ .n_name = "_filehead" },
#define X_TTY_LIST	23
	{ .n_name = "_tty_list" },
#define X_SWTAILQ	24
	{ .n_name = "_swtailq" },         /* list of swap devices and sizes */
#define X_DMMAX		25
	{ .n_name = "_dmmax" },           /* maximum size of a swap block */
#define X_IFNET		26
	{ .n_name = "_ifnet" },
#define X_RTSTAT	27
	{ .n_name = "_rtstat" },
#define X_RTREE		28
	{ .n_name = "_rt_tables"},
#define X_MRTSTAT	29
	{ .n_name = "_mrtstat" },
#define X_MFCTABLE	30
	{ .n_name = "_mfctable" },
#define X_VIFTABLE	31
	{ .n_name = "_viftable" },
#define X_IPX		32
	{ .n_name = "_ipxpcb_list"},
#define X_IPXSTAT	33
	{ .n_name = "_ipxstat"},
#define X_SPXSTAT	34
	{ .n_name = "_spx_istat"},
#define X_DDPSTAT	35
	{ .n_name = "_ddpstat"},
#define X_DDPCB		36
	{ .n_name = "_ddpcb"},
#define X_NGSOCKS	37
	{ .n_name = "_ngsocklist"},
#define X_IP6STAT	38
	{ .n_name = "_ip6stat" },
#define X_ICMP6STAT	39
	{ .n_name = "_icmp6stat" },
#define X_IPSECSTAT	40
	{ .n_name = "_ipsec4stat" },
#define X_IPSEC6STAT	41
	{ .n_name = "_ipsec6stat" },
#define X_PIM6STAT	42
	{ .n_name = "_pim6stat" },
#define X_MRT6STAT	43
	{ .n_name = "_mrt6stat" },
#define X_MF6CTABLE	44
	{ .n_name = "_mf6ctable" },
#define X_MIF6TABLE	45
	{ .n_name = "_mif6table" },
#define X_PFKEYSTAT	46
	{ .n_name = "_pfkeystat" },
#define X_MBSTAT	47
	{ .n_name = "_mbstat" },
#define X_MBTYPES	48
	{ .n_name = "_mbtypes" },
#define X_NMBCLUSTERS	49
	{ .n_name = "_nmbclusters" },
#define X_NMBUFS	50
	{ .n_name = "_nmbufs" },
#define X_MBHI		51
	{ .n_name = "_mbuf_hiwm" },
#define X_CLHI		52
	{ .n_name = "_clust_hiwm" },
#define X_NCPUS		53
	{ .n_name = "_smp_cpus" },
#define X_PAGESZ	54
	{ .n_name = "_pagesize" },
#define X_MBPSTAT	55
	{ .n_name = "_mb_statpcpu" },
#define X_RTTRASH	56
	{ .n_name = "_rttrash" },
#define X_MBLO		57
	{ .n_name = "_mbuf_lowm" },
#define X_CLLO		58
	{ .n_name = "_clust_lowm" },
#define X_CARPSTAT	59
	{ .n_name = "_carpstats" },
#define X_PFSYNCSTAT	60
	{ .n_name = "_pfsyncstats" },
#define X_AHSTAT	61
	{ .n_name = "_ahstat" },
#define X_ESPSTAT	62
	{ .n_name = "_espstat" },
#define X_IPCOMPSTAT	63
	{ .n_name = "_ipcompstat" },
#define X_TCPSTAT	64
	{ .n_name = "_tcpstat" },
#define X_UDPSTAT	65
	{ .n_name = "_udpstat" },
#define X_IPSTAT	66
	{ .n_name = "_ipstat" },
#define X_ICMPSTAT	67
	{ .n_name = "_icmpstat" },
#define X_IGMPSTAT	68
	{ .n_name = "_igmpstat" },
#define X_PIMSTAT	69
	{ .n_name = "_pimstat" },
#define X_TCBINFO	70
	{ .n_name = "_tcbinfo" },
#define X_UDBINFO	71
	{ .n_name = "_udbinfo" },
#define X_DIVCBINFO	72
	{ .n_name = "_divcbinfo" },
#define X_RIPCBINFO	73
	{ .n_name = "_ripcbinfo" },
#define X_UNP_COUNT	74
	{ .n_name = "_unp_count" },
#define X_UNP_GENCNT	75
	{ .n_name = "_unp_gencnt" },
#define X_UNP_DHEAD	76
	{ .n_name = "_unp_dhead" },
#define X_UNP_SHEAD	77
	{ .n_name = "_unp_shead" },
#define X_RIP6STAT	78
	{ .n_name = "_rip6stat" },
#define X_SCTPSTAT	79
	{ .n_name = "_sctpstat" },
#define X_END		80
	{ .n_name = "" }
};

static char		*memf = "/dev/mem", *nlistf = NULL;
static kvm_t		*kd;
static int		fd;
static jmp_buf		jmpbuf;

static void
usage (char* progname) {
	fprintf(stderr, "usage: %s [-M <core>] [-N <system>] <dumpfile>\n", progname);
}

static void
opendump(const char* dumpfile) {
	int	n;
	struct minidumphdr	dumphdr; /* we use minidumphdr for storing pkvmdump info */

	memset(&dumphdr, 0, sizeof(dumphdr));
	strncpy(dumphdr.magic, PKVMDUMP_MAGIC, sizeof(dumphdr.magic) - 1);
	dumphdr.version = PKVMDUMP_VERSION;
	
	fd = open(dumpfile, O_WRONLY | O_CREAT | O_TRUNC, 00666);
	if (fd < 0)
		err(1, "failed to open '%s'", dumpfile);
	
	n = write(fd, &dumphdr, sizeof(dumphdr));
	if (n != sizeof(dumphdr))
		err(1, "failed to write to '%s'", dumpfile);
}

static void
kread(void *src, void *dst, size_t n)
{
	static int	ntries = 0;
        ssize_t		ret;

        ret = kvm_read(kd, (unsigned long) src, dst, n);
        if ((ret < 0) || ((size_t) ret != n)) {
                warn("KVM read failed");
		ntries++;
		close(fd);
		longjmp(jmpbuf, ntries);
	}
}

static void
kread_dump(void *src, void *dst, size_t n)
{
        size_t l;
	struct pkvmdump_entryhdr	ehdr = {(unsigned long) src, n};

	kread(src, dst, n);

	l = write(fd, &ehdr, sizeof(ehdr));
	if (l != sizeof(ehdr))
		err(1, "failed to write to dumpfile");

	l = write(fd, dst, n);
	if (l != n)
		err(1, "failed to write to dumpfile");	

}

static void
dump_region(unsigned long addr, size_t n) {	
	char	buf[1024], *p;
	size_t	m, l;
	struct pkvmdump_entryhdr	ehdr = {addr, n};
	
	l = write(fd, &ehdr, sizeof(ehdr));
	if (l != sizeof(ehdr))
		errx(1, "failed to write to dumpfile");

	p = (char *) addr;
	while (n > 0) {
		m = (n > sizeof(buf)) ? sizeof(buf) : n;
		
		kread(p, buf, m);

		l = write(fd, (const void *) buf, m);
		if (l != m)
			errx(1, "failed to write to dumpfile");
		
		n -= l;
		p += l;
	}
}

static void
dump_string(unsigned long addr) {
	size_t	n;
	char	c, *p;

	n = 0;
	p = (char *) addr;
	do {
		kread(p++, &c, sizeof(c));
		n++;
	} while (c != '\0');
	
	dump_region(addr, n);
}

static void
dump_misc (void) {
	dump_region(namelist[X_BOOTTIME].n_value, sizeof(struct timeval));
	dump_region(namelist[X_STATHZ].n_value,   sizeof(int));
	dump_region(namelist[X_NCHSTATS].n_value, sizeof(struct nchstats));
}

static void
dump_intrpts (void) {
	dump_region(namelist[X_INTRNAMES].n_value,
		    namelist[X_EINTRNAMES].n_value - namelist[X_INTRNAMES].n_value);
	dump_region(namelist[X_INTRCNT].n_value,
		    namelist[X_EINTRCNT].n_value - namelist[X_INTRCNT].n_value);
}

static void
dump_kmemstat (void) {

	int			mp_maxcpus, mp_maxid, i;
	struct pcpu		**pcpu_data;
	struct malloc_type	ks;
	struct malloc_type	*mtp;
	LIST_HEAD(, uma_keg)	uma_kegs;
	__cpumask_t		all_cpus;
	struct uma_bucket	*ubp, ub;
	struct uma_cache	*ucp, *ucp_array;
	struct uma_zone		*uzp, uz;
	struct uma_keg		*kzp, kz;
	
	dump_region(namelist[X_SUM].n_value, sizeof(struct vmmeter));

	kread_dump((void *) namelist[X_MP_MAXCPUS].n_value, &mp_maxcpus,
	      sizeof(mp_maxcpus));
	
	kread_dump((void *) namelist[X_ALL_CPUS].n_value, &all_cpus, sizeof(all_cpus));
	
	pcpu_data = malloc(sizeof(struct pcpu *) * mp_maxcpus);
	if (pcpu_data == NULL)
		err(1, "failed to allocate memory");

	
	kread_dump((void *) namelist[X_CPUID_TO_PCPU].n_value,
	      pcpu_data, sizeof(struct pcpu *) * mp_maxcpus);
	
	for (i = 0; i < mp_maxcpus; i++)
		if (all_cpus & (1 << i))
			dump_region((unsigned long) pcpu_data[i],
				    sizeof(struct pcpu));
	free(pcpu_data);
	
	kread_dump((void *) namelist[X_KMEMSTATS].n_value, &mtp, sizeof(mtp));
	for (; mtp != NULL; mtp = ks.ks_next) {
		kread_dump(mtp, &ks, sizeof(ks));
		dump_string((unsigned long) ks.ks_shortdesc);
#if __FreeBSD_version < 702000 /* XXX I am not sure when this changed */
		dump_region((unsigned long) ks.ks_handle,
			    sizeof(struct malloc_type_stats) * mp_maxcpus);
#else
		dump_region((unsigned long) ks.ks_handle,
			    sizeof(struct malloc_type_internal) -
			    sizeof(struct malloc_type_stats) * (MAXCPU - mp_maxcpus));
#endif
	}
	
	kread_dump((void *) namelist[X_MP_MAXID].n_value, &mp_maxid, sizeof(mp_maxid));
	
	kread_dump((void *) namelist[X_UMA_KEGS].n_value, &uma_kegs, sizeof(uma_kegs));

	ucp_array = malloc(sizeof(struct uma_cache) * (mp_maxid + 1));
	if (ucp_array == NULL)
		err(1, "failed to allocate memory");
	
	for (kzp = LIST_FIRST(&uma_kegs); kzp != NULL;
	     kzp = LIST_NEXT(&kz, uk_link)) {
		
		kread_dump(kzp, &kz, sizeof(kz));
		
		for (uzp = LIST_FIRST(&kz.uk_zones); uzp != NULL;
		     uzp = LIST_NEXT(&uz, uz_link)) {
			
			kread_dump(uzp, &uz, sizeof(uz));
			
			kread_dump((void *) ((unsigned long) uzp + offsetof(struct uma_zone, uz_cpu[0])),
			      ucp_array, sizeof(struct uma_cache) * (mp_maxid + 1));

			dump_string((unsigned long) uz.uz_name);

			if (kz.uk_flags & UMA_ZFLAG_INTERNAL)
				continue;

			/* per cpu */			
			for (i = 0; i < mp_maxid + 1; i++) {
				if ((all_cpus & (1 << i)) == 0)
					continue;
				
				ucp = &ucp_array[i];
				
				if (ucp->uc_allocbucket != NULL)
					dump_region((unsigned long) ucp->uc_allocbucket,
						    sizeof(struct uma_bucket));
				
				if (ucp->uc_freebucket != NULL)
					dump_region((unsigned long) ucp->uc_freebucket,
						    sizeof(struct uma_bucket));
				
				for (ubp = LIST_FIRST(&uz.uz_full_bucket); ubp != NULL;
				     ubp = LIST_NEXT(&ub, ub_link)) {
					kread_dump(ubp, &ub, sizeof(ub));
				}
			}
		}
	}
	free(ucp_array);
}

static void
dump_vnode(struct vnode *vp) {

	struct vnode	vn;
	char		tagstr[12], *tagptr;
			
	kread_dump(vp, &vn, sizeof(vn));
	kread_dump(&vp->v_tag, &tagptr, sizeof(tagptr));
	kread_dump(tagptr, tagstr, sizeof(tagstr));	
	tagstr[sizeof(tagstr) - 1] = '\0';
	
	if ((vn.v_type != VNON) && (vn.v_type != VBAD)) {
		if(strcmp("ufs", tagstr) == 0) {
#include <ufs/ufs/inode.h>
			dump_region((unsigned long) VTOI(&vn),
				    sizeof(struct inode));
#undef doff_t       /* XXX it is better to place isofs stuff to separate file and don't do this hack */
#undef IN_ACCESS
#undef VTOI					
		} else if (strcmp("devfs", tagstr) == 0) {
			
			struct devfs_dirent devfs_dirent;
					
			kread_dump(vn.v_data, &devfs_dirent, sizeof (devfs_dirent));
			dump_region((unsigned long) vn.v_mount,
				    sizeof(struct mount));
			dump_region((unsigned long) devfs_dirent.de_vnode,
				    sizeof (struct vnode));
			
		} else if (strcmp("nfs", tagstr) == 0) {
			
			dump_region((unsigned long) VTONFS(&vn),
						    sizeof(struct nfsnode));
					
		} else if (strcmp("msdosfs", tagstr) == 0) {
					
			struct denode denode;
			
#define VTODE(vp) ((struct denode *)(vp)->v_data)
			
			kread_dump(VTODE(&vn), &denode, sizeof(denode));
			dump_region((unsigned long) denode.de_pmp,
				    sizeof(struct msdosfsmount));
						
		} else if (strcmp("isofs", tagstr) == 0) {
#include <isofs/cd9660/cd9660_node.h>
			dump_region((unsigned long) VTOI(&vn),
				    sizeof(struct iso_node));
			
		}
	}

	if(vn.v_mount)
		dump_region((unsigned long) vn.v_mount, sizeof(struct mount));
}


static void
dump_files(struct proc *proc) {
	struct file	file, **ofiles;
	struct filedesc	filed;
	int		i;


	if ((proc->p_state == PRS_ZOMBIE) ||
	    (proc->p_fd == NULL))
		return;
		
	kread_dump(proc->p_fd, &filed, sizeof(filed));

	/*
	 * root directory vnode, if one
	 */
	if (filed.fd_rdir)
		dump_vnode(filed.fd_rdir);
	/*
	 * current working directory vnode
	 */
	if (filed.fd_cdir)
		dump_vnode(filed.fd_cdir);
	/*
	 * jail root, if any.
	 */
	if (filed.fd_jdir)
		dump_vnode(filed.fd_jdir);
	/*
	 * ktrace vnode, if one
	 */
	if (proc->p_tracevp)
		dump_vnode(proc->p_tracevp);
	/*
	 * text vnode, if one
	 */
	if (proc->p_textvp)
		dump_vnode(proc->p_textvp);
	
	/*
	 * open files
	 */
#define FPSIZE	(sizeof(struct file *))
#define MAX_LASTFILE	(0x1000000)
	
	/* Sanity check on filed.fd_lastfile */
	if ((filed.fd_lastfile < 0) ||
	    (filed.fd_lastfile > MAX_LASTFILE))
		return;
	
	ofiles = malloc(FPSIZE * (filed.fd_lastfile + 1));
	if (ofiles == NULL)
		err(1, "failed to allocate memory");
	
	kread_dump(filed.fd_ofiles, ofiles,
		   FPSIZE * (filed.fd_lastfile + 1));
	
	for (i = 0; i <= filed.fd_lastfile; i++) {
		
		if (ofiles[i] == NULL)
			continue;
		
		kread_dump(ofiles[i], &file, sizeof(file));
		
		if (file.f_type == DTYPE_VNODE)
			dump_vnode(file.f_vnode);
		else if (file.f_type == DTYPE_SOCKET)
			/* sockets are dumped in dump_pcblists() */ ;
#ifdef DTYPE_PIPE
		else if (file.f_type == DTYPE_PIPE)
			dump_region((unsigned long) file.f_data, sizeof(struct pipe));
#endif
#ifdef DTYPE_FIFO
		else if (file.f_type == DTYPE_FIFO)
			dump_vnode(file.f_vnode);
#endif
	}
	
	free(ofiles);
}

static void
dump_proc (void) {
	int		i;
	struct proc	*p, *procs[2];
	struct proc	proc;
	struct thread	mtd;
	struct ucred	ucred;
	struct pgrp	pgrp;
	struct session	sess;
	struct tty	tty;
	struct sysentvec	sysent;

	dump_region(namelist[X_TICKS].n_value,  sizeof(int));
	dump_region(namelist[X_HZ].n_value,     sizeof(int));
	dump_region(namelist[X_NPROCS].n_value, sizeof(int));

	kread_dump((void *) namelist[X_ALLPROC].n_value, &procs[0],
		   sizeof(procs[0]));

	kread_dump((void *) namelist[X_ZOMBPROC].n_value, &procs[1],
		   sizeof(procs[1]));

	for (i = 0; i < 2; i++) {
		for (p = procs[i]; p != NULL; p = LIST_NEXT(&proc, p_list)) {
			kread_dump(p, &proc, sizeof(proc));

			if (proc.p_state != PRS_ZOMBIE) {
				kread_dump(TAILQ_FIRST(&proc.p_threads), &mtd,
					   sizeof(mtd));
			}

			if (proc.p_ucred != NULL) {
				kread_dump(proc.p_ucred, &ucred, sizeof(ucred));		
				if (ucred.cr_prison != NULL)
					dump_region((unsigned long) ucred.cr_prison,
						    sizeof(struct prison));
			}
				
			if (proc.p_sigacts != NULL)
				dump_region((unsigned long) proc.p_sigacts,
					    sizeof(struct sigacts));
		
			if (!proc.p_oppid && proc.p_pptr)
				dump_region((unsigned long) proc.p_pptr,
					    sizeof(struct proc));

			if (proc.p_pgrp != NULL) {
				
				kread_dump(proc.p_pgrp, &pgrp, sizeof(pgrp));
			
				kread_dump(pgrp.pg_session, &sess, sizeof(sess));
				
				if ((proc.p_flag & P_CONTROLT) && sess.s_ttyp != NULL) {
			
					kread_dump(sess.s_ttyp, &tty, sizeof(tty));
			
					if (tty.t_dev != NULL)
						dump_region((unsigned long) tty.t_dev,
							    sizeof(struct cdev));

					if (tty.t_pgrp != NULL)
						dump_region((unsigned long) tty.t_pgrp,
							    sizeof(struct pgrp));

					if (tty.t_session != NULL)
						dump_region((unsigned long) tty.t_session,
							    sizeof(struct session));
				}
				
				if ((proc.p_state != PRS_ZOMBIE) && mtd.td_wmesg)
					dump_region((unsigned long) mtd.td_wmesg,
						    WMESGLEN);

				dump_region((unsigned long) proc.p_vmspace,
					    sizeof(struct vmspace));

				kread_dump(proc.p_sysent, &sysent, sizeof(sysent));

				dump_region((unsigned long) sysent.sv_name,
					    KI_EMULNAMELEN);
				
				if ((proc.p_state != PRS_ZOMBIE)
				    && mtd.td_blocked && mtd.td_lockname)
					dump_region((unsigned long) mtd.td_lockname,
						    LOCKNAMELEN);
			}

			dump_files(&proc);
		}
	}
}

static void
dump_tty (void) {
	TAILQ_HEAD(, tty)	tl;
	struct tty		*tp, tty;

	kread_dump((void *) namelist[X_TTY_LIST].n_value, &tl, sizeof(tl));

	for (tp = TAILQ_FIRST(&tl); tp != NULL; tp = TAILQ_NEXT(&tty, t_list))
		kread_dump(tp, &tty, sizeof(tty));

}

static void
dump_filestat (void) {

#if __FreeBSD_version < 800000	/* XXX: not sure we need thia at all */
	struct filelist	fhead;
	struct file	file, *fp;
#endif	
	dump_region(namelist[X_MAXFILES].n_value, sizeof(int));
	dump_region(namelist[X_OPENFILES].n_value, sizeof(int));

#if __FreeBSD_version < 800000	
	if (!namelist[X_FILEHEAD].n_value)
		return;	
	kread_dump((void *) namelist[X_FILEHEAD].n_value,
		   &fhead, sizeof(fhead));
	LIST_FOREACH(fp, &fhead, f_list) {
		kread_dump(fp, &file, sizeof(file));
		fp = &file;
	}
#endif
}

static void
dump_swapinfo (void) {

	TAILQ_HEAD(, swdevt)	swtailq;
	struct swdevt		*sp, swinfo;

	kread_dump((void *) namelist[X_SWTAILQ].n_value,
	      &swtailq, sizeof(swtailq));

	for (sp = TAILQ_FIRST(&swtailq); sp != NULL; sp = TAILQ_NEXT(&swinfo, sw_list))
		kread_dump(sp, &swinfo, sizeof(swinfo));
}

static void
dump_mbstat (void) {
	
	dump_region(namelist[X_MBSTAT].n_value, sizeof(struct mbstat));	

}

static void
dump_pcblists (void) {
	struct inpcbinfo	pcbinfo;
	struct inpcbhead	listhead;
	struct inpcb		*inp;
	struct xtcpcb		xt;
	struct xinpcb		xi;
	struct socket		so;
	int			i;
	int	idx[] = {X_TCBINFO,
			 X_UDBINFO,
			 X_DIVCBINFO,
			 X_RIPCBINFO};
	struct unp_head	head;
	struct unpcb	*unp, unp_conn;
	u_char		sun_len;
	unp_gen_t	unp_gencnt;
	u_int		unp_count;
	struct xunpcb	xu;
	unsigned long	head_off;
	int		type;
	struct protosw	proto;
	struct domain	dom;

	for(i=0; i < (int) (sizeof(idx) / sizeof(int)); i++) {
		if (namelist[idx[i]].n_value == 0)
			continue;
		kread_dump((void *) namelist[idx[i]].n_value,
		      &pcbinfo, sizeof(pcbinfo));
		kread_dump(pcbinfo.ipi_listhead, &listhead, sizeof(listhead));
		
		LIST_FOREACH(inp, &listhead, inp_list) {
			if (idx[i] == X_TCBINFO) {
				kread_dump(inp, &xt.xt_inp, sizeof(*inp));
				inp = &xt.xt_inp;
			} else {
				kread_dump(inp, &xi.xi_inp, sizeof(*inp));
				inp = &xi.xi_inp;
			}
			if ((idx[i] == X_TCBINFO) && inp->inp_ppcb &&
			    !(inp->inp_flags & INP_TIMEWAIT))
				dump_region((unsigned long) inp->inp_ppcb,
					    sizeof(struct tcpcb));
			if (inp->inp_socket) {
				kread_dump(inp->inp_socket, &so, sizeof(so));
				kread_dump(so.so_proto, &proto, sizeof(proto));
				kread_dump(proto.pr_domain, &dom, sizeof(dom));
				dump_region((unsigned long) dom.dom_name,
					    12 * sizeof(char));
			}
		}

		/* unix sockets */

		for (type = SOCK_STREAM; type <= SOCK_SEQPACKET; type++) {
			
			if (type == SOCK_STREAM )
				head_off = namelist[X_UNP_SHEAD].n_value;
			else if (type == SOCK_DGRAM)
				head_off = namelist[X_UNP_DHEAD].n_value;
			else
				continue;
		
			kread_dump((void *) namelist[X_UNP_COUNT].n_value,
				   &unp_count, sizeof(unp_count));
		
			kread_dump((void *) namelist[X_UNP_GENCNT].n_value,
				   &unp_gencnt, sizeof(unp_gencnt));
		
			kread_dump((void *) head_off, &head, sizeof(head));
			
			LIST_FOREACH(unp, &head, unp_link) {
				kread_dump(unp, &xu.xu_unp, sizeof (*unp));
				unp = &xu.xu_unp;
				
				if (unp->unp_addr != NULL) {
					kread_dump(unp->unp_addr, &sun_len, sizeof(sun_len));
					dump_region((unsigned long) unp->unp_addr, sun_len);
				}
				if (unp->unp_conn != NULL) {
					kread_dump(unp->unp_conn, &unp_conn, sizeof(unp_conn));
					if (unp_conn.unp_addr != NULL) {
						kread_dump(unp_conn.unp_addr, &sun_len,
						      sizeof(sun_len));
						dump_region((unsigned long) unp_conn.unp_addr,
							    sun_len);
					}
				}
				kread_dump(unp->unp_socket, &so, sizeof(so));
				kread_dump(so.so_proto, &proto, sizeof(proto));
				kread_dump(proto.pr_domain, &dom, sizeof(dom));
				dump_region((unsigned long) dom.dom_name,
					    12 * sizeof(char));
			}
		}
	}
}

static void
dump_netstat (void) {

	if (namelist[X_TCPSTAT].n_value)
		dump_region(namelist[X_TCPSTAT].n_value, sizeof(struct tcpstat));
	if (namelist[X_UDPSTAT].n_value)
		dump_region(namelist[X_UDPSTAT].n_value, sizeof(struct udpstat));
	if (namelist[X_SCTPSTAT].n_value)
		dump_region(namelist[X_SCTPSTAT].n_value, sizeof(struct sctpstat));
	if (namelist[X_IPSTAT].n_value)
		dump_region(namelist[X_IPSTAT].n_value, sizeof(struct ipstat));
	if (namelist[X_ICMPSTAT].n_value)
		dump_region(namelist[X_ICMPSTAT].n_value, sizeof(struct icmpstat));
	if (namelist[X_IGMPSTAT].n_value)
		dump_region(namelist[X_IGMPSTAT].n_value, sizeof(struct igmpstat));
	if (namelist[X_IPSECSTAT].n_value)
		dump_region(namelist[X_IPSECSTAT].n_value, sizeof(struct ipsecstat));
	if (namelist[X_AHSTAT].n_value)
		dump_region(namelist[X_AHSTAT].n_value, sizeof(struct ahstat));
	if (namelist[X_ESPSTAT].n_value)
		dump_region(namelist[X_ESPSTAT].n_value, sizeof(struct espstat));
	if (namelist[X_IPCOMPSTAT].n_value)
		dump_region(namelist[X_IPCOMPSTAT].n_value, sizeof(struct ipcompstat));
	if (namelist[X_PIMSTAT].n_value)
		dump_region(namelist[X_PIMSTAT].n_value, sizeof(struct pimstat));
	if (namelist[X_CARPSTAT].n_value)
		dump_region(namelist[X_CARPSTAT].n_value, sizeof(struct carpstats));
	if (namelist[X_PFSYNCSTAT].n_value)
		dump_region(namelist[X_PFSYNCSTAT].n_value, sizeof(struct pfsyncstats));
}

static void
dump_if (void) {

        struct ifnet		ifnet;
        struct ifnethead	ifnethead;
	union {
		struct ifaddr		ifa;
		struct in_ifaddr	in;
		struct in6_ifaddr	in6;
		struct ipx_ifaddr	ipx;
	}			ifaddr;
	void			*ifnetaddr;
	void			*ifaddraddr;
	void			*ifaddrfound;
	void			*ifnetfound;
	

	if (!namelist[X_IFNET].n_value)
		return;
	
	kread_dump((void *) namelist[X_IFNET].n_value, (char *)&ifnethead,
		   sizeof(ifnethead));

	ifnetaddr = TAILQ_FIRST(&ifnethead);
        kread_dump(ifnetaddr, (char *)&ifnet, sizeof(ifnet));
	ifaddraddr = NULL;
	while (ifnetaddr || ifaddraddr) {
		if (ifaddraddr == NULL) {
			ifnetfound = ifnetaddr;
			kread_dump(ifnetaddr, (char *)&ifnet, sizeof(ifnet));
			ifnetaddr = TAILQ_NEXT(&ifnet, if_link);
			ifaddraddr = TAILQ_FIRST(&ifnet.if_addrhead);
		}
		ifaddrfound = ifaddraddr;
		if (ifaddraddr != NULL) {
			kread_dump(ifaddraddr, &ifaddr, sizeof(ifaddr));
			ifaddraddr = TAILQ_NEXT(&ifaddr.ifa, ifa_link);
		}

		if (ifaddrfound != NULL) {
			struct ifmultiaddr *multiaddr;
			struct ifmultiaddr ifma;
			union {
				struct sockaddr sa;
				struct sockaddr_in in;
				struct sockaddr_in6 in6;
				struct sockaddr_dl dl;
			} msa;
			TAILQ_FOREACH(multiaddr, &ifnet.if_multiaddrs, ifma_link) {
				kread_dump(multiaddr, (char *)&ifma, sizeof(ifma));
				dump_region((unsigned long) ifma.ifma_addr, sizeof(msa));
				multiaddr = &ifma;
			}
		}
	}
}

struct rtline  {
	struct	radix_node_head *tables[AF_MAX+1]; /*XXX*/
};

static void
dump_route (void) { /* XXX not implemented yet */
#if __FreeBSD_version < 800000	
	struct radix_node_head	*rnh, head;
	struct rtline		rt_tables;
	int			i;

	if (!namelist[X_RTREE].n_value)
		return;
	
	kread_dump((void *) namelist[X_RTREE].n_value,
		   &rt_tables, sizeof(rt_tables));
	
	for (i = 0; i <= AF_MAX; i++) {
		if ((rnh = rt_tables.tables[i]) == 0)
			continue;
		kread_dump(rnh, &head, sizeof(head));
	}
#endif
}

int
main (int argc, char * argv[]) {
char *		dumpfile;
	char		errbuf[_POSIX2_LINE_MAX];
	int		c, i;

        while ((c = getopt(argc, argv, "M:N:")) != -1) {
		switch(c) {
		case 'N':
			nlistf = optarg;
			break;
		case 'M':
			memf = optarg;
			break;
		default:
			usage(argv[0]);
			exit(1);
		}
	}
	
	argc -= optind;
	
	if (argc != 1) {
		usage(argv[0]);
		exit(1);
	}

	argv += optind;
	
	dumpfile = argv[0];

	kd = kvm_openfiles(nlistf, memf, NULL, O_RDONLY, errbuf);

	if (kd == NULL)
		err(1, "kvm_openfiles: %s", errbuf);

	if (kvm_nlist(kd, namelist) < 0)
		err(1, "kvm_nlist failed to read symbols");

	/*
	 * XXX As we don't lock kernel structures they can change
	 * during dumpimg and kvm_kread will fail. In this case we
	 * just restart dump. If several tries have failed we gives
	 * up. Memory leak is possible when kvm_reading to allocated
	 * region and it fails and dumping restarts but currently we
	 * don't mind as it is short living program.
	 */

	i = setjmp(jmpbuf);
	if (i > 3)
		errx(1, "giving up");
	else if (i > 0)
		warnx("Retrying");
		
	opendump(dumpfile);

	dump_misc();
	dump_intrpts();
	dump_kmemstat();
	dump_proc();
	dump_tty();
	dump_filestat();
	dump_swapinfo();
	dump_mbstat();
	dump_pcblists();
	dump_netstat();
	dump_if();
	dump_route();
	
	if (close(fd) != 0)
		err(1, "failed to close '%s'", dumpfile);
	
	if (kvm_close(kd) != 0)
		err(1, "failed to close KVM");
	
	return 0;

}
